#pragma once

#include<iostream>
#include<memory>
#include<unistd.h>
#include<sys/epoll.h>
#include"Socket.hpp"
#include"Epoller.hpp"

#define SIZE 1024

uint32_t EVENT_IN = (EPOLLIN);
uint32_t EVENT_OUT = (EPOLLOUT);

class EpollServer
{
public:
    static const int num = 64;

    EpollServer(uint16_t port)
    :_port(port)
    ,_listensock_ptr(new Sock())
    ,_epoller_ptr(new Epoller())
    {}

    bool Init()
    {
        _listensock_ptr->Socket();
        _listensock_ptr->Bind(_port);
        _listensock_ptr->Listen();

        lg(Info, "create listensock success: %d", _listensock_ptr->Fd());

        return true;
    }

    void Accepter()
    {
        string clientip;
        uint16_t clientport;
        int sock = _listensock_ptr->Accept(&clientip, &clientport);
        if(sock > 0)
        {
            //我们得继续把返回的sock添加到epoll模型的红黑树中, 来让epoll下一次关注这个文件描述符
            _epoller_ptr->EpollerUpdate(EPOLL_CTL_ADD, sock, EVENT_IN);

            lg(Info, "get a new link, client info@ %s:%d", clientip.c_str(), clientport);
        }
    }

    void Recever(int fd)
    {
        char buffer[SIZE];

        ssize_t n = read(fd, buffer, sizeof(buffer));
        if(n > 0)
        {
            buffer[n] = 0;
            cout << "get a message: " << buffer << endl;

            string buffer_str = buffer;
            string echo_str = "server echo $ " + buffer_str;
            write(fd, echo_str.c_str(), echo_str.size());
        }
        else if(n == 0)
        {
            cout << "client quit, me too!" << endl;
            _epoller_ptr->EpollerUpdate(EPOLL_CTL_DEL, fd, 0);
            close(fd);
        }
        else
        {
            cerr << "read error" << endl;
            _epoller_ptr->EpollerUpdate(EPOLL_CTL_DEL, fd, 0);
            close(fd);
        }
    }

    void Dispatcher(struct epoll_event revs[], int num)
    {
        //传到这里是已经就绪的文件描述符和他对应的事件
        for(int i = 0; i < num; i++)
        {
            uint32_t event = revs[i].events;
            int fd = revs[i].data.fd;
            //到这里就可以知道哪个文件描述符的哪个时间就绪

            if(event & EVENT_IN)//读事件
            {
                if(fd == _listensock_ptr->Fd())
                {
                    //获取新的链接
                    Accepter();
                }
                else
                {
                    //正常套接字
                    Recever(fd);
                }
            }
            else if(event & EVENT_OUT)//写事件
            {

            }
            else
            {
                //异常事件
            }
        }
    }

    void Start()
    {
        int listensock = _listensock_ptr->Fd();

        struct epoll_event revs[num];
        //我们需要把listensock添加到epoll模型中的rb树中
        //epoll模型中不用维护用户层的数组, 直接使用系统调用来添加就可以
        _epoller_ptr->EpollerUpdate(EPOLL_CTL_ADD, listensock, EVENT_IN);

        while(true)
        {
            int n = _epoller_ptr->EpollerWait(revs, num);
            if(n > 0)
            {
                //有事件就绪
                lg(Debug, "event happened, fd is: %d", revs[0].data.fd);
                Dispatcher(revs, n);//注意这里不能填写num而是填写n，因为n代表由n个就绪的事件成功返回
            }
            else if(n == 0)
            {
                lg(Info, "time out...");
            }
            else
            {
                lg(Error, "epoll wait error");
            }
        }
    }

    ~EpollServer()
    {
        _listensock_ptr->Close();
    }

private:
    EpollServer(const EpollServer& ep) = delete;
    const EpollServer& operator=(const EpollServer& ep) = delete;

private:
    shared_ptr<Sock> _listensock_ptr;
    shared_ptr<Epoller> _epoller_ptr;
    uint16_t _port;
};  